perf(cloudflare): publish unaffected static app routes with config transforms#2510
Draft
NathanDrake2406 wants to merge 8 commits into
Draft
perf(cloudflare): publish unaffected static app routes with config transforms#2510NathanDrake2406 wants to merge 8 commits into
NathanDrake2406 wants to merge 8 commits into
Conversation
Fully static App Router prerender outputs currently stay server-owned, so Workers still execute the App Router request lifecycle for cache-hit document and RSC responses. That is unnecessary when Cloudflare Assets can represent the response and no middleware, config transforms, slash redirect, basePath, or i18n routing can observe the request. Add a conservative build-time publisher that copies eligible static App Router HTML/RSC artifacts into the Cloudflare assets directory and emits matching _headers. Skip ISR, Pages routes, /404 and /500 status routes, existing asset collisions, middleware/proxy projects, and request-transforming config. Tests cover publication, generated headers, middleware/config opt-outs, ISR/pages/status-route skips, and asset-collision skips.
Cloudflare asset publication for prerendered App Router routes could publish static HTML and RSC files from route metadata alone. That bypassed the runtime cache guard that only serves query-bearing requests when render observation proves searchParams were not observed. The publisher now requires prerender query-invariance metadata before exposing static App assets. Prerender records the proof using the same render observation check as the runtime cache path, and RSC assets require their own proof bit.
The request-pipeline test hard-codes the Vinext-only internal header set. The new prerender query-invariance side-channel is intentionally internal, so CI failed until the test covered that header as stripped and separate from Next.js INTERNAL_HEADERS.
Cloudflare asset publication could still publish generated HTML when a matching RSC asset already existed and generated RSC publication was disabled by query proof. That left Cloudflare serving mixed route semantics: generated HTML for the document request and an unrelated existing asset for the RSC request. The route collision guard now treats any existing RSC target as a route-level collision, independent of whether generated RSC will be copied. The regression test pins the html-proof true, rsc-proof false case with an existing about.rsc asset.
Static App Router HTML assets can bypass the Worker on Cloudflare, but browser RSC requests cannot share the public document URL because Assets matches before the Worker sees RSC headers. Publishing document HTML at the visible route path without a separate RSC transport lets /about?_rsc collide with /about. Route RSC requests through a Cloudflare-only split transport: plain proven-static RSC uses a reserved static asset namespace, variant RSC uses a Worker-owned namespace that maps back to the visible route before dispatch, and publication stays gated by query proof plus request-transform safety checks. Tests cover Cloudflare transport URL selection, stale transport redirect canonicalisation, reserved RSC publication, and collision safety.
…aders Static App Router asset publication emitted one _headers rule per HTML and RSC artifact and enabled the client transport from root Wrangler config only. On Workers Static Assets, matching header rules inherit and duplicate values are joined, and selected environments can change assets.not_found_handling. That made broad user rules able to corrupt static RSC cache headers, pushed publishable apps toward Cloudflare's header-rule limit, and allowed client RSC transport generation to disagree with emitted asset publication. Resolve Wrangler assets through a shared CLOUDFLARE_ENV-aware helper, fail publication closed when the selected environment disables the transport, collapse static RSC protocol headers to one splat rule, and detach inherited protocol headers before setting generated values.
…ansforms Cloudflare static App Router asset publication skipped the whole app whenever next.config headers, redirects, or rewrites existed. That was safe, but it made an unrelated /legacy redirect prevent /about from taking the static asset path. The skipped-app assumption was broader than the routing invariant requires: only a route whose visible pathname can match a request transform must remain Worker-owned. Reuse config source matching to skip affected routes while publishing routes whose source patterns cannot match. Request-dependent has/missing conditions still fail closed because a matching source skips publication before evaluating request state.
commit: |
Contributor
Performance benchmarksCompared 0 improved · 0 regressed · 6 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
f7ba54b to
ee658ed
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
next.configrequest transforms.mainuntil #2506 lands.cloudflare-prerender-assets.ts,config-matchers.ts,cloudflare-prerender-assets.test.tsWhy
A request transform is only semantically relevant to a static asset route when that transform can match the same visible route path. If
/legacyredirects to/about, that should not prevent publishing/aboutas a static prerendered asset. The safe boundary is route-local eligibility: affected routes still fall through to the Worker, unaffected routes can keep the static transport win.What changed
Benchmark
Existing benchmark evidence for this route-eligibility delta, captured before the rebase onto the latest #2506 review fixes:
tests/fixtures/cf-app-basiccopy without middleware, plus unrelatednext.configredirect/legacy -> /aboutwrangler dev/workerdee7cfc46e8325981f5before rebase, same route-eligibility change now atee658ed3cSanity from the same run:
/aboutHTML asset/aboutstatic RSC assetValidation
vp fmt packages/vinext/src/build/cloudflare-prerender-assets.ts packages/vinext/src/config/config-matchers.ts tests/cloudflare-prerender-assets.test.tsvp test run tests/app-rsc-cache-busting.test.ts tests/cloudflare-prerender-assets.test.ts tests/cloudflare-rsc-transport-config.test.tsvp check packages/vinext/src/build/cloudflare-static-assets-config.ts packages/vinext/src/build/cloudflare-prerender-assets.ts packages/vinext/src/config/config-matchers.ts packages/vinext/src/index.ts tests/cloudflare-prerender-assets.test.ts tests/cloudflare-rsc-transport-config.test.tsvp run vinext#buildNon-goals
basePath,i18n,trailingSlash, or non-query-invariant prerenders.